#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#include "fs4412_pwm.h"

MODULE_LICENSE("GPL");


#define PWM_MA 501
#define PWM_MI 0
#define PWM_NUM 1


struct fs4412_pwm
{
	unsigned int *gpd0con;
	unsigned int *timer_base;
	struct cdev cdev;
};

static struct fs4412_pwm *pwm;
	
static int fs4412_pwm_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int fs4412_pwm_io_init()
{
	writel((readl(pwm->gpd0con)&~0xf)|0x2,pwm->gpd0con);								//����GPD0CON���ƼĴ���[3:0]��ֵΪ0x2, ��TOUT0�źŻ����PWM�źš�
	writel((readl(pwm->timer_base+TCFG0)&~0xff)|0xC7,pwm->timer_base+TCFG0);       //һ����Ƶ  ��ƵֵΪ199  ��200��Ƶ   199��16����Ϊ0xC7
	writel((readl(pwm->timer_base+TCFG1)&~0xf)|0x1,pwm->timer_base+TCFG1);      //������Ƶ��ѡ��ƵֵΪ1/2 ��2��Ƶ
	writel(600,pwm->timer_base+TCNTB0);      //pwm��ֵΪ600
	writel(300,pwm->timer_base+TCMPB0);   //pwm��ƽ��תֵΪ300
	writel((readl(pwm->timer_base+TCON)&~0xf)|0x2,pwm->timer_base+TCON);   //��ʱ���ֶ����£�������TCNTB0 �� TCMPB0��ֵ

	return 0;
}

static int fs4412_pwm_rlease(struct inode *inode, struct file *file)
{
	writel(readl(pwm->timer_base + TCON) & ~0xf, pwm->timer_base + TCON);
	return 0;
}

static long fs4412_pwm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int data;
	if (_IOC_DIR(cmd) == _IOC_WRITE)
		if (copy_from_user(&data, (void *)arg, sizeof(data)))
			return -EFAULT;
	switch(cmd)
	{
	case PWM_ON:
		//���üĴ���TCON��ʹ��ʱ���������������Զ�����
		writel((readl(pwm->timer_base+TCON)&~0xf)|0x9,pwm->timer_base+TCON);
		break;
	case PWM_OFF:
		//���üĴ���TCON��ʹ��ʱ���رգ�����λ����
		writel(readl(pwm->timer_base+TCON)&~0xf,pwm->timer_base+TCON);
		break;
	case SET_PRE:
		//���üĴ���TCFG0��ֵ���Ͱ�λ�����㣬�ٸ��Ӧ�ó�����ݴ���������ݽ��еͰ�λ����
		writel((readl(pwm->timer_base+TCFG0)&~0xff)|(data&0xff),pwm->timer_base+TCFG0);
		//��ʱ���������������Զ�����
		writel((readl(pwm->timer_base+TCON)&~0xf)|0x9,pwm->timer_base+TCON);
		break;
	case SET_CNT:
		 //����pwm��ʱ���ĳ�ֵTCNB0
		writel(data,pwm->timer_base+TCNTB0);
		 //����pwm��ʱ���ķ�תֵTCMPB0
		writel(data/2,pwm->timer_base+TCMPB0);
		break;
	}

	return 0;
}
	
static struct file_operations fs4412_pwm_fops = {
	.owner = THIS_MODULE,
	.open = fs4412_pwm_open,
	.release = fs4412_pwm_rlease,
	.unlocked_ioctl = fs4412_pwm_ioctl,
};

static int __init fs4412_pwm_init(void)
{
	int ret;
	dev_t devno = MKDEV(PWM_MA, PWM_MI); //�������豸�Ż��dev_t�ĺ�

	ret = register_chrdev_region(devno, PWM_NUM, "pwm");//Ϊ������豸�ţ�Ϊע���豸��׼��
	if (ret < 0) {
		printk("faipwm : register_chrdev_region\n");
		return ret;
	}

	pwm = kmalloc(sizeof(*pwm), GFP_KERNEL);//���ں˿ռ�Ϊָ�������ַ
	if (pwm == NULL) {
		ret = -ENOMEM;
		printk("faipwm: kmalloc\n");
		goto err1;
	}
	memset(pwm, 0, sizeof(*pwm)); //ָ���ʼ��

	cdev_init(&pwm->cdev, &fs4412_pwm_fops);//ע���ļ������ַ�����cdev��file_operation֮�������
	pwm->cdev.owner = THIS_MODULE;
	ret = cdev_add(&pwm->cdev, devno, PWM_NUM);//ע���豸
	if (ret < 0) {
		printk("faipwm: cdev_add\n");
		goto err2;
	}

	pwm->gpd0con = ioremap(GPD0CON, 4);//�����Ʒ�������Ź��ܵĿ��ƼĴ�����ַӳ��Ϊ�����ַ
	if (pwm->gpd0con == NULL) {
		ret = -ENOMEM;
		printk("faipwm: ioremap gpd0con\n");
		goto err3;
	}

	pwm->timer_base = ioremap(TIMER_BASE, 0x20);//����ʱ���Ļ��ַӳ��Ϊ�����ַ
	if (pwm->timer_base == NULL) {
		ret = -ENOMEM;
		printk("failed: ioremap timer_base\n");
		goto err4;
	}

	fs4412_pwm_io_init();
	return 0;

err4:
	iounmap(pwm->gpd0con);
err3:
	cdev_del(&pwm->cdev);
err2:
	kfree(pwm);
err1:
	unregister_chrdev_region(devno, PWM_NUM);
	return ret;
}

static void __exit fs4412_pwm_exit(void)
{
	dev_t devno = MKDEV(PWM_MA, PWM_MI);
	iounmap(pwm->timer_base);
	iounmap(pwm->gpd0con);//ȡ���ַӳ�䣬��Ӧfs4412_pwm_init()�е�ioremap()
	cdev_del(&pwm->cdev);////ע���豸  ��Ӧfs4412_pwm_init()�е�cdev_add����
	kfree(pwm); //�ͷ�pwmָ��
	unregister_chrdev_region(devno, PWM_NUM);//�ͷ�fs4412_pwm_init()��ͨ��register_chrdev_region����ע����豸��

}

module_init(fs4412_pwm_init);
module_exit(fs4412_pwm_exit);
